package cn.edu.sjtu.stap.cfg;

import cn.edu.sjtu.stap.graph.*;

public class ControlFlowGraph extends Graph {
    private static final long serialVersionUID = 1L;

    public static final int METHOD_CFG = 1;

    public static final int CONSTRUCTOR_CFG = 2;

    public static final int ARTIFICIAL_METHOD_CFG = 3;

    private CfgEntry entryNode;

    private CfgReturningExit returningExitNode;

    private CfgThrowingExit throwingExitNode;

    private String name;

    /**
     * CFG for the whole.
     * 
     */
    public ControlFlowGraph(ControlFlowGraph main) {
        this.entryNode = main.entryNode;
        this.returningExitNode = main.returningExitNode;
        this.throwingExitNode = main.throwingExitNode;

        this.add(main);
    }

    /**
     * CFG for a single function. The kind of the function to make CFG for
     * should be provided. It makes the entry node, returning exit node,
     * throwing exit node for this CFG, and adds them into this graph.
     * 
     * @param kind
     *            the kind of the function to make CFG for.
     */
    public ControlFlowGraph(int kind) {
        CfgNodeKind entryKind = null;
        CfgNodeKind rExitKind = null; // Returning exit.
        CfgNodeKind tExitKind = null; // Throwing exit.
        // int flowKind = CfgFlowKind.NORMAL_FLOW;

        switch (kind) {
        // CFG for constructor.
        case CONSTRUCTOR_CFG:
            entryKind = CfgNodeKind.CONSTRUCTOR_ENTRY;
            rExitKind = CfgNodeKind.CONSTRUCTOR_RETURNING_EXIT;
            tExitKind = CfgNodeKind.CONSTRUCTOR_THROWING_EXIT;
            break;

        // CFG for artificial method which is for the proceed().
        case ARTIFICIAL_METHOD_CFG:
            entryKind = CfgNodeKind.ARTIFICIAL_METHOD_ENTRY;
            rExitKind = CfgNodeKind.ARTIFICIAL_METHOD_RETURNING_EXIT;
            tExitKind = CfgNodeKind.ARTIFICIAL_METHOD_THROWING_EXIT;
            break;

        // CFG for method.
        case METHOD_CFG:
        default:
            entryKind = CfgNodeKind.METHOD_ENTRY;
            rExitKind = CfgNodeKind.METHOD_RETURNING_EXIT;
            tExitKind = CfgNodeKind.METHOD_THROWING_EXIT;
            // Set by default.
            break;
        }

        // Make initial nodes.
        entryNode = new CfgEntry(entryKind);
        returningExitNode = new CfgReturningExit(rExitKind);
        throwingExitNode = new CfgThrowingExit(tExitKind);

        // Add these nodes into CFG.
        this.addNode(entryNode);
        this.addNode(returningExitNode);
        this.addNode(throwingExitNode);
        this.addFlow(entryNode, returningExitNode);
    }

    public void addNode(CfgNode node) {
        this.add(node);
    }

    public void addFlow(CfgNode from, CfgNode to) {
        this.addFlow(CfgFlowKind.NORMAL_FLOW, from, to);
    }

    public void addFlow(CfgFlowKind kind, CfgNode from, CfgNode to) {
        CfgFlow flow = new CfgFlow(kind, from, to);

        this.add(flow);
    }

    public void addControlFlowGraph(ControlFlowGraph graph, CfgNode from, CfgNode to, CfgFlowKind inKind,
            CfgFlowKind outKind) {
        // Add the given CFG into this CFG.
        this.add(graph);

        // Connect the given CFG with this CFG at (from, to).
        CfgFlow flow = this.getFlow(from, to);
        if (flow != null) {
            this.removeFlow(flow);
        }
        this.addFlow(inKind, from, graph.getCfgEntry());
        this.addFlow(outKind, graph.getCfgReturningExit(), to);
    }

    public void clear() {
        super.clear();

        this.addNode(entryNode);
        this.addNode(returningExitNode);
        this.addNode(throwingExitNode);
    }

    public boolean removeFlow(CfgFlow flow) {
        return this.remove(flow);
    }

    public boolean removeFlow(CfgNode from, CfgNode to) {
        return this.remove(from, to);
    }

    public CfgFlow getFlow(CfgNode from, CfgNode to) {
        GraphEdge[] edges = this.getEdges(from, to);
        if (edges.length > 0) {
            return (CfgFlow) edges[0];
        }

        return null;
    }

    public CfgEntry getCfgEntry() {
        return entryNode;
    }

    public CfgReturningExit getCfgReturningExit() {
        return returningExitNode;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
